mod body;
pub mod configuration;

use configuration::BoxedError;
pub use configuration::Configuration;
pub use hyper::{self, StatusCode, Uri};
pub use url::Url;

use std::{error, fmt, ops::Deref, sync::Arc};

#[derive(Clone)]
pub struct ApiClient {
{{#apiInfo}}
{{#apis}}
{{#operations}}
    {{{classFilename}}}: Box<dyn crate::apis::{{{classFilename}}}::tower::client::{{{classname}}}>,
{{/operations}}
{{/apis}}
{{/apiInfo}}
}

/// Same as `ApiClient` but returns the body directly.
pub mod direct {
    #[derive(Clone)]
    pub struct ApiClient {
    {{#apiInfo}}
    {{#apis}}
    {{#operations}}
        {{{classFilename}}}: Box<dyn crate::apis::{{{classFilename}}}::tower::client::direct::{{{classname}}}>,
    {{/operations}}
    {{/apis}}
    {{/apiInfo}}
    }

    impl ApiClient {
        pub fn new(configuration: super::Configuration) -> ApiClient {
            let rc = super::Arc::new(configuration);

            ApiClient {
    {{#apiInfo}}
    {{#apis}}
    {{#operations}}
                {{^-last}}
                {{{classFilename}}}: Box::new(crate::apis::{{{classFilename}}}::tower::client::{{{classname}}}Client::new(rc.clone())),
                {{/-last}}
                {{#-last}}
                {{{classFilename}}}: Box::new(crate::apis::{{{classFilename}}}::tower::client::{{{classname}}}Client::new(rc)),
                {{/-last}}
    {{/operations}}
    {{/apis}}
    {{/apiInfo}}
            }
        }

    {{#apiInfo}}
    {{#apis}}
    {{#operations}}
        pub fn {{{classFilename}}}(&self) -> &dyn crate::apis::{{{classFilename}}}::tower::client::direct::{{{classname}}} {
            self.{{{classFilename}}}.as_ref()
        }
    {{/operations}}
    {{/apis}}
    {{/apiInfo}}
    }
}

impl ApiClient {
    pub fn new(configuration: Configuration) -> ApiClient {
        let rc = Arc::new(configuration);

        ApiClient {
{{#apiInfo}}
{{#apis}}
{{#operations}}
            {{^-last}}
            {{{classFilename}}}: Box::new(crate::apis::{{{classFilename}}}::tower::client::{{{classname}}}Client::new(rc.clone())),
            {{/-last}}
            {{#-last}}
            {{{classFilename}}}: Box::new(crate::apis::{{{classFilename}}}::tower::client::{{{classname}}}Client::new(rc)),
            {{/-last}}
{{/operations}}
{{/apis}}
{{/apiInfo}}
        }
    }

{{#apiInfo}}
{{#apis}}
{{#operations}}
    pub fn {{{classFilename}}}(&self) -> &dyn crate::apis::{{{classFilename}}}::tower::client::{{{classname}}} {
        self.{{{classFilename}}}.as_ref()
    }
{{/operations}}
{{/apis}}
{{/apiInfo}}
}

/// Http Response with status and body.
#[derive(Debug, Clone)]
pub struct ResponseContent<T> {
    pub(crate) status: hyper::StatusCode,
    pub(crate) body: T,
}
impl<T> ResponseContent<T> {
    /// Get the status code.
    pub fn status(&self) -> hyper::StatusCode {
        self.status
    }
    /// Get a reference to the body.
    pub fn body(&self) -> &T {
        &self.body
    }
    /// Convert self into the body.
    pub fn into_body(self) -> T {
        self.body
    }
    /// Convert ResponseContent<T> into ResponseContent<Vec<T>>.
    pub fn with_vec_body(self) -> ResponseContent<Vec<T>> {
        ResponseContent {
            status: self.status,
            body: vec![self.body]
        }
    }
}

/// Http Response with status and body as text (could not be coerced into the expected type).
#[derive(Debug, Clone)]
pub struct ResponseContentUnexpected {
    pub(crate) status: hyper::StatusCode,
    pub(crate) text: String,
}
impl ResponseContentUnexpected {
    /// Get the status code.
    pub fn status(&self) -> hyper::StatusCode {
        self.status
    }
    /// Get a reference to the text.
    pub fn text(&self) -> &str {
        self.text.as_ref()
    }
}

/// Error type for all Requests with the various variants.
#[derive(Debug)]
pub enum Error<T> {
    Request(RequestError),
    Response(ResponseError<T>),
}
impl<T> Error<T> {
    /// Get the request error, if that is the case.
    pub fn request(&self) -> Option<&RequestError> {
        match self {
            Error::Request(request) => Some(request),
            Error::Response(_) => None,
        }
    }
    /// Get the response error, if that is the case.
    pub fn response(&self) -> Option<&ResponseError<T>> {
        match self {
            Error::Request(_) => None,
            Error::Response(response) => Some(response),
        }
    }
    /// Get the expected error, if received.
    pub fn expected(&self) -> Option<&ResponseContent<T>> {
        match self {
            Error::Request(_) => None,
            Error::Response(response) => response.expected(),
        }
    }
    /// Get the inner body error, if expected.
    pub fn error_body(&self) -> Option<&T> {
        match self {
            Error::Request(_) => None,
            Error::Response(response) => response.error_body(),
        }
    }
    /// Get the response status code, if any received.
    pub fn status(&self) -> Option<StatusCode> {
        self.response().map(|response| response.status())
    }
}
impl<T> From<RequestError> for Error<T> {
    fn from(src: RequestError) -> Self {
        Self::Request(src)
    }
}
impl<T> From<ResponseError<T>> for Error<T> {
    fn from(src: ResponseError<T>) -> Self {
        Self::Response(src)
    }
}
impl<T: fmt::Debug> fmt::Display for Error<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::Request(r) => r.fmt(f),
            Error::Response(r) => r.fmt(f),
        }
    }
}
impl<T: fmt::Debug> error::Error for Error<T> {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self {
            Error::Request(r) => r.source(),
            Error::Response(r) => r.source(),
        }
    }
}

/// Failed to issue the request.
#[derive(Debug)]
pub enum RequestError {
    /// Failed to build the http request.
    BuildRequest(hyper::http::Error),
    /// Service Request call returned an error.
    Request(BoxedError),
    /// Service was not ready to process the request.
    NotReady(BoxedError),
    /// Failed to serialize request payload.
    Serde(serde_json::Error),
    /// Failed to encode the url path.
    SerdeEncoded(serde_urlencoded::ser::Error),
}
impl fmt::Display for RequestError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let (module, e) = match self {
            RequestError::BuildRequest(e) => ("build_request", e.to_string()),
            RequestError::Request(e) => ("request", e.to_string()),
            RequestError::NotReady(e) => ("not_ready", e.to_string()),
            RequestError::Serde(e) => ("serde", e.to_string()),
            RequestError::SerdeEncoded(e) => ("serde_encoding", e.to_string()),
        };
        write!(f, "error in {module}: {e}")
    }
}
impl error::Error for RequestError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        Some(match self {
            RequestError::BuildRequest(e) => e,
            RequestError::Request(e) => e.deref(),
            RequestError::NotReady(e) => e.deref(),
            RequestError::Serde(e) => e,
            RequestError::SerdeEncoded(e) => e,
        })
    }
}

/// Error type for all Requests with the various variants.
#[derive(Debug)]
pub enum ResponseError<T> {
    /// The OpenAPI call returned the "expected" OpenAPI JSON content.
    Expected(ResponseContent<T>),
    /// Failed to convert the response payload to bytes.
    PayloadError {
        status: hyper::StatusCode,
        error: hyper::Error,
    },
    /// The OpenAPI call returned an "unexpected" JSON content.
    Unexpected(ResponseContentUnexpected),
}
impl<T: fmt::Debug> fmt::Display for ResponseError<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let (module, e) = match self {
            ResponseError::Expected(e) => (
                "response",
                format!("status code '{}', content: '{:?}'", e.status, e.body),
            ),
            ResponseError::PayloadError { status, error } => (
                "response",
                format!("status code '{status}', error: '{error:?}'"),
            ),
            ResponseError::Unexpected(e) => (
                "response",
                format!("status code '{}', text '{}'", e.status, e.text),
            ),
        };
        write!(f, "error in {module}: {e}")
    }
}
impl<T: fmt::Debug> error::Error for ResponseError<T> {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self {
            ResponseError::Expected(_) => None,
            ResponseError::PayloadError { error, .. } => Some(error),
            ResponseError::Unexpected(_) => None,
        }
    }
}
impl<T> ResponseError<T> {
    /// Get the inner status.
    pub fn status(&self) -> StatusCode {
        match self {
            ResponseError::Expected(expected) => expected.status,
            ResponseError::PayloadError { status, .. } => *status,
            ResponseError::Unexpected(unexpected) => unexpected.status,
        }
    }
    /// Get the expected error, if received.
    pub fn expected(&self) -> Option<&ResponseContent<T>> {
        match self {
            ResponseError::Expected(expected) => Some(expected),
            _ => None,
        }
    }
    /// Get the inner body error, if expected.
    pub fn error_body(&self) -> Option<&T> {
        match self {
            ResponseError::Expected(expected) => Some(&expected.body),
            _ => None,
        }
    }
}

impl std::fmt::Debug for ApiClient {
    fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
        fmt::Result::Ok(())
    }
}
